function handleSuccessfullTreeRetrieval( tree ) {

    globals.tree = eval( "(" + tree + ")" );

    globals.tree.isRoot = true;

    initialize();
    
    viewerWindowOnLoad();
}


function createMainCanvas( p ) {

    //create canvas inside raphaelContainer div
    var canvasObj = Raphael( "raphaelContainer", 300, 100 );
       
    //assign id to canvas DOM  object
    $('#raphaelContainer svg').attr( { id: 'mySVG' } );

    //create svg groups for tip, internal labels
    $('#mySVG').append( makeEl('g').attr( { id: 'tipLabels' } ), makeEl('g').attr( { id: 'internalLabels' } ) );

    return canvasObj;
}


function initialize() {

    var localGlobal = globals;
    var localConfig = config;
    var scaleValue = getScaleValue();

    //if canvas does not exist yet - create it
    if( !localGlobal.r ) { localGlobal.r = createMainCanvas(); }
    
    //traverse the tree for information needed before rendering 
    getTreeInfo( { tree: localGlobal.tree } );
    
    //width of tree only 
    var treeWidth = ( localConfig.generationSeparation.value * ( localGlobal.treeInfo.generations - 1 ) );

    setCanvasSize( { canvas: localGlobal.r, treeWidth: treeWidth, global: localGlobal, config: localConfig, container: 'raphaelContainer', scaleValue: scaleValue } );

    if( localGlobal.scaledBranches ) { localGlobal.lengthMultiplier = treeWidth / localGlobal.treeInfo.longestTraversal; }

    updateLabelSelector();
    
    removeExpandableIcons( { main: true, overview: true } );
    
    clearSupportValueAnnotation();
}

function updateLabelSelector( p ) {

    var localGlobal = globals;

    //set global label object to local variable
    var localGlobalLabels = localGlobal.labelInfo.labels;

    //store labels that we want to keep
    var labelsToKeep = [];

    //loop through labels looking for a corresponding clade - remove labels we can't find a clade for
    for( var id in localGlobalLabels ) {

        if( ! findClade( { id: id, clade: localGlobal.tree } ) ) {

            localGlobalLabels[id].hide();
            localGlobalLabels[id].remove();
            delete localGlobalLabels[id];

            $('#'+id).remove();

        } else {

            labelsToKeep.push( { text: localGlobalLabels[id].attr('text'), id: id } );

        }
    }

    //if we're keep all labels then return
    if( localGlobalLabels.length == labelsToKeep.length ) { return; }

    updateNodeSuggest( { labels: labelsToKeep } );
}

function autoSuggestSelectionMade( p ) {

    var id = p.attributes.id;
                                           
    toggleHighlight( id );
    $('.as-close').click();
    if( $('#' + id).hasClass('highlight') ) { scrollToNode( { id: id } ); }
}


function labelSelectorOptionsOnClick() { showNodeSelectorOptions(); }
        
function createHomeAndHideLinks( p ) {

    $('#leftPanel').prepend(
        makeEl('a').attr( { 'class': 'smallText', 'id': 'hideButton', href: 'javascript:void(0);' } ).click( hideLeftPanelOnClick ).text('Hide')
                   .css( { position: 'absolute', left: p.mainPanelOffset.left - 30, top: p.mainPanelOffset.top + 5 } ),
        
        makeEl('div').attr( { 'class': 'smallText' } ).append(
            makeEl('a').attr( { href: '/' + globals.applicationName + '/default/index' } ).text('Home')
                       .css( { position: 'absolute', left: p.leftPanelOffset.left + 5, top: p.mainPanelOffset.top + 5 } ) ) );
}

function createSliders( p ) {

    $('#visual_container').prepend(
        makeEl('div').attr( { 'id': 'vertSlider', 'class': 'absoluteFront' } )
                     .css( { left: p.mainPanelOffset.left + 15,
                             top: p.mainPanelOffset.top + 20 } )
                     .slider( { orientation: 'vertical',
                                disabled: true,
                                stop: handleSlide,
                                value: 100 } ),

        makeEl('div').attr( { 'id': 'horiSlider', 'class': 'absoluteFront' } )
                     .css( { left: p.mainPanelOffset.left + 35,
                             top: p.mainPanelOffset.top + 20 } )
                     .slider( { disabled: true, stop: handleSlide, value: 100 } ) );
}

function onTabSelection( undefined, ui ) {

    if( ui.index == 0 ) { setTimeout( alignLens, 100 ); }
    else if( ui.index == 1 ) { setTimeout( alignNodeSelectorTab, 100 ); };
}

function onColorSelection( color ) { globals.selectedCladeColor = color; }

function initializeTabs() {

    var tabHeight = $('#visual_container').height() - $('#tabs').offset().top;

    $('#tabs').tabs( { disabled: [1, 2], select: onTabSelection } ).height( tabHeight );

    var tabContentHeight = tabHeight - $('#tabSelector').height();
        
    $('#fragment-1').height( tabContentHeight );
    $('#fragment-2').height( tabContentHeight );
    $('#fragment-3').height( tabContentHeight );

    $('#colorPickerContainer').farbtastic( onColorSelection );
}

function initializeScrollAndLens() {
        
    $('#visual_container').scrollLeft( globals.canvas.width - $('#visual_container').width() + globals.scrollBarWidth );
    $('#draggableLens').offset( { top: $('#scaledTreeContainer').offset().top, left: $('#draggableLens').offset().left } );
}

function handleMouseDown( e ) {
    
    //if its a right click we don't care
    if( e.which == 3 ) { return; }

    if( mouseOnScrollBar( { x: e.pageX, y: e.pageY } ) ||
        mouseOnElement( { x: e.pageX, y: e.pageY, el: $('#vertSlider') } ) ||
        mouseOnElement( { x: e.pageX, y: e.pageY, el: $('#vertSlider > a') } ) ||
        mouseOnElement( { x: e.pageX, y: e.pageY, el: $('#horiSlider > a') } ) ||
        mouseOnElement( { x: e.pageX, y: e.pageY, el: $('#horiSlider') } ) ) { return; }
    else { globals.currentEvents.mouseDown = true; }
}


function mouseOnScrollBar( p ) {

    var mainPanel = $('#visual_container');
    var mainPanelOffset = mainPanel.offset();
    var mainPanelHeight = mainPanel.height();
    var mainPanelWidth = mainPanel.width();
    var localGlobals = globals;

    //if there is a vertical scrollbar, let's make sure the mouse isn't being clicked to scroll it
    if( ( mainPanelHeight < localGlobals.canvas.height ) && 
        ( p.x > ( mainPanelOffset.left + mainPanelWidth - localGlobals.scrollBarWidth ) ) ) { return true; }

    //if there is a horizontal scrollbar, let's make sure the mouse isn't being clicked to scroll it
    else if( ( mainPanelWidth < localGlobals.canvas.width ) &&
             ( p.y > ( mainPanelOffset.top + mainPanelHeight - localGlobals.scrollBarWidth ) ) ) { return true; }

    return false;
}

function handleMouseUp( e ) {

    var localGlobal = globals;

    var contextMenu = isContextMenu();

    //if right click
    if( e.which == 3 ) {
        
        if( contextMenu ) { contextMenu.empty().remove(); return; }
        else if( localGlobal.cladeButton && !localGlobal.currentEvents.loadingLabels) { handleRightClick( { x: e.pageX, y: e.pageY, global: localGlobal } ); } }

    else {

        localGlobal.currentEvents.mouseDown = false;

        if( contextMenu ) {

            if( mouseOnElement( { x: e.pageX, y: e.pageY, el: contextMenu } ) ) { return true; }
            else { contextMenu.empty().remove(); return false; }
        }

        var dragger = $('#dragger');

        if( dragger.length > 0 ) {

            if( !localGlobal.currentEvents.shiftDown ) { clearSelection(); }

            dragSelectClades( { dragger: dragger, global: localGlobal } );
            
            dragger.remove();
        }

        else if( mouseOnScrollBar( { x: e.pageX, y: e.pageY } ) ) { return; }
        
        else if( mouseOnElement( { x: e.pageX, y: e.pageY, el: $('#vertSlider') } ) ||
                 mouseOnElement( { x: e.pageX, y: e.pageY, el: $('#vertSlider > a') } ) ||
                 mouseOnElement( { x: e.pageX, y: e.pageY, el: $('#horiSlider > a') } ) ||
                 mouseOnElement( { x: e.pageX, y: e.pageY, el: $('#horiSlider') } ) ) { return; }

        else { if( !localGlobal.currentEvents.shiftDown ) { clearSelection(); } }

        return true;
    }
}


function addMainPanelEvents() {

    $('#visual_container').scroll( handleScroll );

    $('#visual_container').mousemove( handleMouseMove );

    $('#visual_container').mousedown( handleMouseDown );

    $('#visual_container').mouseup( handleMouseUp );
}

function handleDocumentKeyDown( e ) { if( e.keyCode == 16 ) { globals.currentEvents.shiftDown = true; } }
function handleDocumentKeyUp( e ) { if( e.keyCode == 16 ) { globals.currentEvents.shiftDown = false; } }

function handleDocumentMouseUp( e ) {

    var leftPanelPopup = $('#panelItemOptionsPopup');

    if( leftPanelPopup.length ) {

        var popupOffset = leftPanelPopup.offset();

        if( ( e.pageX < popupOffset.left ) ||
            ( e.pageX > popupOffset.left + leftPanelPopup.width() ) ||
            ( e.pageY < popupOffset.top ) ||
            ( e.pageY > popupOffset.top + leftPanelPopup.height() ) ) {
    
                $('#panelItemOptionsPopup').empty().remove();
        }
    }
}

function setOverviewCanvas( p ) {

    var canvasObj = p.global.overviewR;


    var overviewCanvasSize = { height: Math.max( $('#fragment-1').height(), $('#fragment-2').height(), $('#fragment-3').height() ),
                               width: Math.max( $('#fragment-1').width(), $('#fragment-2').width(), $('#fragment-3').width() ) };

    p.global.overviewScale = { x: ( overviewCanvasSize.width  / ( p.global.canvas.width ) ),
                               y: ( overviewCanvasSize.height / ( p.global.canvas.height ) ) };

    if( canvasObj ) {
     
        var overviewObjs = p.global.pathObjs.overview;
         
        overviewObjs.tree.remove(); 

        if( overviewObjs.supportValue ) { overviewObjs.supportValue.remove(); }

        canvasObj.setSize( overviewCanvasSize.width, overviewCanvasSize.height );

    } else { 

        canvasObj = Raphael( "scaledTreeContainer", overviewCanvasSize.width, overviewCanvasSize.height );
    }

    return canvasObj;
}

function determineSupportValueSupport( p ) {

    var rv = { main: undefined, overview: undefined };

    if( ( p.info.lastSupportValue != 'disable' && p.info.lastSupportValue != undefined) || p.info.threshold ) { p.info.showOption = true; }
    
    if( p.info.threshold ) {

        rv.main = createTreePath( { string: p.pathStrings.main.toString(),
                                    canvas: p.canvas.main,
                                    attr: { stroke: p.pathColor, "stroke-width": p.info.thickness } } );
        
        rv.overview = createTreePath( { string: p.pathStrings.overview.toString(),
                                        canvas: p.canvas.overview,
                                        attr: { stroke: p.pathColor, "stroke-width": p.info.thickness * p.overviewScale.y } } );
    }
        
    return rv;
}

function drawCladogram( p ) {

    var firstTip = { x: ( p.config.generationSeparation.value * ( p.global.treeInfo.generations - 1 ) + 20 + p.config.canvasBuffer.value ), //add 20 for root
                     y: ( p.config.canvasBuffer.value + p.global.treeInfo.exampleLabelHeight ) };
    
    preRender( { clade: p.global.tree,
                scaledBranches: p.global.scaledBranches,
                currentTip: firstTip,
                scaleValue: p.scaleValue,
                generationSeparation: p.config.generationSeparation.value,
                nodeSeparation: p.config.nodeSeparation.value,
                lengthMultiplier: p.global.lengthMultiplier,
                scaling: p.global.scaling,
                overviewScale: p.global.overviewScale,
                supportValueInfo: p.global.supportValueInfo,
                pathWidth: p.config.pathWidth.value,
                fontSize: p.config.fontSize.value,
                buttonRadius: p.config.buttonRadius.value,
                labelBuffer: p.config.labelBuffer.value,
                pathStrings: p.global.pathStrings,
                labels: p.global.labelInfo.labels,
                labelsToAdd: p.global.labelInfo.labelsToAdd,
                renderInfoFun: gatherCladeRenderInfo,
                canvas: { main: p.global.r, overview: p.global.overviewR },
                generation: p.global.treeInfo.generations } );


    var smoothCoordsParams =
        { clade: p.global.tree,
          scaling: p.global.scaling,
          overviewScale: p.global.overviewScale,
          scaleValue: p.scaleValue,
          supportValueInfo: p.global.supportValueInfo,
          fontSize: p.config.fontSize.value,
          buttonRadius: p.config.buttonRadius.value,
          labelBuffer: p.config.labelBuffer.value,
          labels: p.global.labelInfo.labels,
          labelsToAdd: p.global.labelInfo.labelsToAdd,
          pathWidth: p.config.pathWidth.value,
          pathStrings: p.global.pathStrings,
          renderInfoFun: gatherCladeRenderInfo,
          canvas: { main: p.global.r, overview: p.global.overviewR },
          leftmostStart: p.global.canvas.width };

    if( p.global.scaling == 'smooth') {

        findSmoothCoords( smoothCoordsParams );

    } else {

        setTimeout( findSmoothCoords, 0, smoothCoordsParams );
    }
                   
    var labelInfo = p.global.labelInfo;

    if( labelInfo.labelsToAdd.length ) {

        setTimeout( addLabels, 0, { labels: labelInfo.labelsToAdd,
                                    labelHolder: labelInfo.labels,
                                    canvas: p.global.r,
                                    fragment: p.global.fragment,
                                    scaling: p.global.scaling,
                                    localConfig: { labelColor: p.config.labelColor.value,
                                                   fontSize: p.config.fontSize.value,
                                                   fontFaily: p.config.fontFamily.value,
                                                   buttonRadius: p.config.buttonRadius.value,
                                                   labelBuffer: p.config.labelBuffer.value },
                                    index: 0 } );
    }
        

    p.global.pathObjs.main.tree =
        createTreePath( { string: p.global.pathStrings.main.tree.toString(),
                          canvas: p.global.r,
                          attr: { stroke: p.config.cladeColor.value,
                                  "stroke-width": p.config.pathWidth.value } } );

    p.global.pathObjs.overview.tree = 
        createTreePath( { string: p.global.pathStrings.overview.tree.toString(),
                          canvas: p.global.overviewR,
                          attr: { stroke: p.config.cladeColor.value,
                              "stroke-width": .5 } } );

    var supportPathObjs = determineSupportValueSupport( { info: p.global.supportValueInfo,
                                                   canvas: { main: p.global.r,
                                                             overview: p.global.overviewR },
                                                   pathColor: p.config.cladeColor.value,
                                                   overviewScale: p.global.overviewScale,
                                                   pathStrings: { main: p.global.pathStrings.main.supportValue,
                                                                  overview: p.global.pathStrings.overview.supportValue } } );

    p.global.pathObjs.main.supportValue = supportPathObjs.main;
    p.global.pathObjs.overview.supportValue = supportPathObjs.overview;
}

function viewerWindowOnLoad() {

    var localGlobal = globals;
    var localConfig = config;

    addShadowboxKeyPressHandlers();

    setLadderizeItem();
    
    initializeTabs();
    
    var scaleValue = { x:1,y:1 };
  
    localGlobal.overviewR = setOverviewCanvas( { global: localGlobal, scaleValue: scaleValue } );

    localGlobal.scrollBarWidth = getScrollBarWidth();

    drawCladogram( { global: localGlobal, config: localConfig, scaleValue: scaleValue } );
    
    $(document).ready( onViewerDocumentReady );
    
}


function setLensSize() {

   var localGlobal = globals;

   var scaleValue = getScaleValue();

   var mainPanelDims = { y: $('#visual_container').height(), x: $('#visual_container').width() };
   var raphaelContainerDims = { y: $('#raphaelContainer').height(), x: $('#raphaelContainer').width() };

   var mainPanelSize = { y: mainPanelDims.y - ( raphaelContainerDims.x > mainPanelDims.x ) ? localGlobal.scrollBarWidth : 0,
                         x: mainPanelDims.x - ( raphaelContainerDims.y > mainPanelDims.y ) ? localGlobal.scrollBarWidth : 0 };

   //var lensSize = { y: $('#scaledTreeContainer').height() * ( mainPanelDims.y / raphaelContainerDims.y ),
                    //x: $('#scaledTreeContainer').width() * ( mainPanelDims.x / raphaelContainerDims.x ) };
   
   var lensSize = { y: localGlobal.overviewR.height * ( mainPanelDims.y / raphaelContainerDims.y ),
                    x: localGlobal.overviewR.width * ( mainPanelDims.x / raphaelContainerDims.x ) };

   var overviewCanvasSize = { height: localGlobal.overviewR.height, width: localGlobal.overviewR.width };

   if( lensSize.x > localGlobal.overviewR.width ) { lensSize.x = localGlobal.overviewR.width; }
   if( lensSize.y > localGlobal.overviewR.height ) { lensSize.y = localGlobal.overviewR.height; }
   
   $('#draggableLens').height( lensSize.y ).width( lensSize.x ); 
}

function createLens() {

    $('#scaledTreeContainer').prepend(
        makeEl('div').attr( { 'id': 'draggableLens', 'class': 'transparent draggableLens' } )
                     .draggable( { containment: 'parent',
                                 drag: handleDrag,
                                 start: setDragging,
                                 stop: unsetDragging } ) );

    setLensSize();
}

function onViewerDocumentReady() {

    $('#labelSelectorOptions').click( labelSelectorOptionsOnClick );

    var containerOffset = $('#visual_container').offset();
    var leftPanelOffset = $('#leftPanel').offset();

    createHomeAndHideLinks( { leftPanelOffset: leftPanelOffset, mainPanelOffset: containerOffset } );

    createSliders( { mainPanelOffset: containerOffset } );

    createLens();

    initializeScrollAndLens();

    addMainPanelEvents();

    $(document).bind('keydown', handleDocumentKeyDown ).bind('keyup', handleDocumentKeyUp );

    $(window).resize( handleWindowResize );

    $(document).mouseup( handleDocumentMouseUp );
}


function updateLabel( p ) {

    var localGlobal = globals;
    var localConfig = config;
    var localLabels = localGlobal.labelInfo.labels;

    var isAdd;

    //if an empty value is passed to this function - the user edited a label to an empty string 
    //so we remove the label the user did not add a blank label
    if( p.value == '' ) { localLabels[ p.id ].remove(); delete localLabels[ p.id ]; $('#'+p.id).remove(); }


    //if this an edit label (with text left), remove the old label before we create a new one 
    if( localLabels[ p.id ] ) { localLabels[ p.id ].remove(); $('#'+p.id).text( p.value ); }

    //if this is an add label we need to create a new row in the label selector panel
    else { addLabelSelectorRow( { text: p.value, id: p.id } ); isAdd = true; }


    //gather attributes needed for label rendering
    var clade = findClade( { id: p.id, clade: localGlobal.tree } );
    clade.name = p.value;

    var attrs = { canvas: localGlobal.r,
                  label: { x: clade.coords[globals.scaling].x, index: clade.topoIndex,
                           y: clade.coords.y, text: p.value, isInternal: clade.children.length },
                  localConfig: { labelColor: localConfig.labelColor.value,
                                 fontSize: localConfig.fontSize.value,
                                 fontFaily: localConfig.fontFamily.value,
                                 buttonRadius: localConfig.buttonRadius.value,
                                 labelBuffer: localConfig.labelBuffer.value } };

    //render label and store Obj
    localLabels[ p.id ] = drawCladeLabel( attrs );

    if( isAdd ) { sortLabelSelectorRows(); }

    //the library used for the auto sugget doesn't allow an update, so we have to recreate an array
    //of labels and re initialize the auto suggest - should probably just write my own auto suggest
    //this is wasteful 
    
    var autoSuggestLabelList = [];
    for( var id in localLabels ) { autoSuggestLabelList.push( { text: localLabels[id].attr('text'), id: id } ); }
    updateNodeSuggest( { labels: autoSuggestLabelList } );

    labelBox = localLabels[p.id].getBBox();
 
    //if the label is long and reaches to either end of the canvas, redraw properly 
    if( ( labelBox.x + labelBox.width ) > localGlobal.canvas.width || labelBox.x <= 0 ) { redrawCladogram(); }
}

function sortLabelSelectorRows() {

    if( globals.topologicalSort ) { topologicallySortLabels(); }
    else { $("#nodeSelector > div").sort( alphabetizeLabels ); }
}

function alphabetizeLabels( a, b ) { return ( $(a).text() > $(b).text() ) ? 1 : -1; }

function topologicallySortLabels( a, b ) {

    var localLabels = globals.labelInfo.labels;

    $("#nodeSelector > div").sort(
        function(a,b) {
            return localLabels[ $(a).attr('id') ].topoIndex > localLabels[ $(b).attr('id') ].topoIndex;
        } );
}

function toggleHighlight( p ) {

    if( $('#'+p.id).hasClass('highlight') ) {

        $('#'+p.id).removeClass('highlight');

        removeSelection( { id: p.id } );

        checkForZeroSelectionRows();

    } else {

        scrollToNode( { id: p.id, fromClick: true } );

        $('#'+p.id).addClass('highlight');
    
        selectDescendants( { id: p.id } );
    }
}

function checkForZeroSelectionRows() {

    if( $('#nodeSelector > .highlight').length == 0 ) {
        $('#reRootLink').addClass('disabled').unbind('click');
        $('#pruneLink').addClass('disabled').unbind('click');
    }
}

function removeSelection( p ) {
    
    var selectedClades = globals.selectedClades;

    selectedClades.main[ p.id ].hide().remove();
    delete selectedClades.main[ p.id ];
    
    selectedClades.scaled[ p.id ].hide().remove();
    delete selectedClades.scaled[ p.id ];

}

function reAlignButton() {

    var localGlobal = globals;

    var localButton = localGlobal.cladeButton;
    var localButtonRadius = config.buttonRadius.value;

    if( !localButton ) { return; }

    var node = findClade( { id: localButton.id, clade: localGlobal.tree } );  if( !node ) { localButton.remove(); return; }

    var buttonBox = localButton.getBBox();

    localButton.translate( node.coords[localGlobal.scaling].x - buttonBox.x - localButtonRadius,
                           node.coords.y - buttonBox.y - localButtonRadius );

    localButton.toFront();
}

function addToSupportValueStrings( p ) {

    var coords = p.clade.coords;
    var X = p.clade.coords[p.scaling].x;
    var DX = p.clade.coords[p.scaling].dx;

    p.pathStrings.main.append( "M" + X + " " + ( coords.y + ( p.pathWidth / 2 ) ) + "l" + DX + " 0 " );
    
    p.pathStrings.overview.append( "M" + ( X * p.overviewScale.x ) + " " + ( ( coords.y + ( p.pathWidth / 2 ) ) * p.overviewScale.y ) +
                                   "l" + ( DX * p.overviewScale.x ) + " 0 " );
}

function addNodeToTreePath( p ) {

    var coords = { x: p.clade.coords[p.scaling].x,
                  dx: p.clade.coords[p.scaling].dx,
                   y: p.clade.coords.y };

    var mainCoords = { x: coords.x * p.scaleValue.x,
                      dx: coords.dx * p.scaleValue.x,
                       y: coords.y * p.scaleValue.y };
    
    var overviewCoords = { x: coords.x * p.overviewScale.x,
                          dx: coords.dx * p.overviewScale.x,
                           y: coords.y * p.overviewScale.y };

    if( p.clade.isRoot ) {

        p.pathStrings.main.append( "M" ).append( mainCoords.x ).append( " " ).append( mainCoords.y ).append( "l-20 0" );

        p.pathStrings.overview.append( "M" ).append( overviewCoords.x ).append( " " ).append( overviewCoords.y )
                            .append( "l" ).append( -20 * p.overviewScale.x ).append( " 0" );

    } else {

        p.pathStrings.main.append( "M" ).append( mainCoords.x ).append( " " ).append( mainCoords.y ).append( "l" ).append( mainCoords.dx ).append( " 0" );

        p.pathStrings.overview.append( "M" ).append( overviewCoords.x ).append( " " ).append( overviewCoords.y )
                            .append( "l" ).append( overviewCoords.dx ).append( " 0" );
    }

    if( p.clade.children.length ) {

        var firstChildY = p.clade.children[0].coords.y;
        var lastChildY = p.clade.children[p.clade.children.length-1].coords.y;

        p.pathStrings.main.append( "M" ).append( mainCoords.x ).append( " " ).append( firstChildY * p.scaleValue.y )
                        .append( "L" ).append( mainCoords.x ).append( " " ).append( lastChildY * p.scaleValue.y );

        p.pathStrings.overview.append( "M" ).append( overviewCoords.x ).append( " " ).append( firstChildY * p.overviewScale.y )
                              .append( "L" ).append( overviewCoords.x ).append( " " ).append( lastChildY * p.overviewScale.y );
    }
}

function gatherCladeRenderInfo( p ) {
    
    var currentCoords = p.clade.coords;

    addNodeToTreePath( { pathStrings: { main: p.pathStrings.main.tree,
                                        overview: p.pathStrings.overview.tree },
                         clade: p.clade,
                         scaling: p.scaling,
                         scaleValue: p.scaleValue,
                         overviewScale: p.overviewScale } );
   
    if( p.clade.isExpandable == 'True' ) {

        drawExpandableIcon( { canvas: p.canvas.main,
                              type: 'main',
                              cladeId: p.clade.id,
                              point: { x: currentCoords[ p.scaling ].x * p.scaleValue.x,
                                       y: currentCoords.y * p.scaleValue.y },
                              size: p.scaleValue.y * 5 } );
        
        drawExpandableIcon( { canvas: p.canvas.overview,
                              type: 'overview',
                              cladeId: p.clade.id,
                              point: { x: currentCoords[ p.scaling ].x * p.overviewScale.x,
                                       y: currentCoords.y * p.overviewScale.y },
                              size: p.overviewScale.y * 5 } );
    }

    if( p.clade.name && p.supportValueInfo.threshold &&
        parseInt( p.clade.name ) >= p.supportValueInfo.threshold ) {
            
                addToSupportValueStrings( { clade: p.clade,
                                            scaling: p.scaling,
                                            overviewScale: p.overviewScale,
                                            pathWidth: p.pathWidth,
                                            pathStrings: { main: p.pathStrings.main.supportValue,
                                                           overview: p.pathStrings.overview.supportValue } } );
    }
    
    if( p.clade.name ) {

        handleLabel( { clade: p.clade,
                       labels: p.labels,
                       labelsToAdd: p.labelsToAdd,
                       fontSize: p.fontSize,
                       scaleValue: p.scaleValue,
                       buttonRadius: p.buttonRadius,
                       supportValueInfo: p.supportValueInfo,
                       scaling: p.scaling,
                       labelBuffer: p.labelBuffer } );
    }
}

function checkForSupportValue( p ) {

    var svInfo = p.supportValueInfo;
    var labelText = p.clade.name;

    if( (!svInfo.threshold) && svInfo.lastSupportValue != 'disable' ) {

        if( svInfo.lastSupportValue == undefined ) {

            if( labelText.match(/^\d+$/) ) { svInfo.lastSupportValue = parseInt(labelText); } else { svInfo.lastSupportValue = 'disable'; }

        } else {

            if( labelText.match(/^\d+$/) ) {

                var currentLabel = parseInt(labelText);

                if( (currentLabel >= 0 && currentLabel <= 1 && svInfo.lastSupportValue >= 0 && svInfo.lastSupportValue <= 1) ||
                    (currentLabel >= 0 && currentLabel <= 100 && svInfo.lastSupportValue >= 0 && svInfo.lastSupportValue <= 100) ) {

                    svInfo.lastSupportValue = currentLabel;

                } else {
                    
                    svInfo.lastSupportValue = 'disable';
                }

            } else {
                
                svInfo.lastSupportValue = 'disable';
            }
        }
    }
}

function drawExpandableIcons( p ) { 

    var expandables = globals.expandable;

    var mainSize = 5 * p.scaleValue.y;
    var overviewSize = 5 * p.overviewScale.y;

    expandables.main[ p.cladeId ] =
        p.canvas.main.path( "M" + (p.point.x - mainSize) + " " + (p.point.y - mainSize) +
                            "L" + (p.point.x + mainSize) + " " + (p.point.y - mainSize) +
                            "L" + p.point.x + " " + (p.point.y + mainSize) + "z").attr( { stroke: 'red', fill: 'red' } );
    
    expandables.overview[ p.cladeId ] =
        p.canvas.overview.path( "M" + ( p.overviewScale.x * (p.point.x - overviewSize) ) + " " + ( p.overviewScale.y * (p.point.y - overviewSize) ) +
                                "L" + ( p.overviewScale.x * (p.point.x + overviewSize) ) + " " + ( p.overviewScale.y * (p.point.y - overviewSize) ) +
                                "L" + ( p.overviewScale.x * p.point.x ) + " " + ( p.overviewScale.y * (p.point.y + overviewSize) ) + "z").attr( { stroke: 'red', fill: 'red' } );
}


function addLabelToFragment( p ) {

    var labelEl = makeEl('text').attr( { 'text-anchor': 'start',
                                         'style': 'font: 14px undefined;',
                                         'font': '10px &quot;Arial&quot;',
                                         'stroke': '#000000',
                                         'fill': '#000000',
                                         'font-family': 'undefined',
                                         'font-size': '14px' } ).append( makeEl('tspan').text( p.label.text ) );
                        

    var translationParams = { isInternal: p.label.isInternal,
                              buttonRadius: p.label.localConfig.buttonRadius,
                              labelBuffer: p.label.localConfig.labelBuffer }

    if( p.label.isInternal ) { translationParams.labelWidth = $(labelEl).width(); }

    var pointTranslation = getLabelPointTranslation( translationParams );

    p.fragment.appendChild( labelEl );
}

function addLabels( p ) {

    globals.currentEvents.loadingLabels = true;
    
    disableUntilAllLabelsCreated();

    createProgressBar( { container: $('#visual_container') } );

    asyncAddLabels( p );
}

function asyncAddLabels( p ) {

    $('#labelProgressBar').progressbar( 'option', 'value', ( p.index / p.labels.length ) * 100 );

    addLabelSelectorRow( { text: p.labels[p.index].text, id: p.labels[p.index].id } );

    p.labelHolder[ p.labels[p.index].id ] =
        drawCladeLabel( { canvas: p.canvas, localConfig: p.localConfig, label: p.labels[p.index++] } );

    if( p.index < p.labels.length ) {

        setTimeout( asyncAddLabels, 0, { labels: p.labels, index: p.index, labelHolder: p.labelHolder, scaling: p.scaling,
                                         canvas: p.canvas, localConfig: p.localConfig } );

    } else {

        updateNodeSuggest( { labels: p.labels } );
        enablePostLabelOptions();

        if( p.scaling == 'smooth') { sortLabelSelectorRows(); }

        //why
        //p.labels = [ ];
        globals.labelInfo.labelsToAdd = [];
    }
}

function alignNodeSelectorTab() {
    
    var tabFragmentHeight = Math.max( $('#fragment-1').height(), $('#fragment-2').height(), $('#fragment-3').height() );

    $('#nodeSelector').height( tabFragmentHeight - $('#nodeSelectorContainer').height() - $('#nodeSuggestDiv').height() - $('#nodeCleanDiv').height() - 10 );

}

function addLabelSelectorRow( p ) {

    $('#nodeSelector').append(
        makeEl('div').text( p.text )
                     .attr( { 'id': p.id } )
                     .click( labelSelectorClick )
                     .mouseover( addAltHighlight )
                     .mouseout( removeAltHighlight ) );
}

function labelSelectorClick() { toggleHighlight( { id: $(this).attr('id') } ); }
function addAltHighlight() { $(this).addClass('altHighlight'); }
function removeAltHighlight() { $(this).removeClass('altHighlight'); }


function enablePostLabelOptions( p ) {
    
    globals.currentEvents.loadingLabels = false;

    $('#horiSlider').slider( 'option', 'disabled', false );
    $('#vertSlider').slider( 'option', 'disabled', false );

    $('#tabs').tabs( 'option', 'disabled', [] );

    $('#labelProgressBar').empty().remove();
    $('#labelProgressBarHeader').remove();

    var panelFunctionMapper = { scaleItem: showScalingOptions,
                                ladderizeItem: ladderizeRoot,
                                labelItem: showLabelOptions,
                                optionsItem: showOtherOptions,
                                exportItem: showExportOptions,
                                zoomItem: showZoomOptions };

    if( globals.supportValueInfo.showOption ) {

        panelFunctionMapper['supportValueItem'] = showSupportValueOptions;

    } else {

        $('#supportValueItem').hide();
        $('#scaleItem').removeClass('width33').addClass('width50');
        $('#ladderizeItem').removeClass('width33').addClass('width50');

    }

    for( var id in panelFunctionMapper ) { $('#'+id).click( panelFunctionMapper[id] ).removeClass('disabledPanelItem').addClass('panelItem'); }
}

function ladderizeRoot() { ajaxLadderize( { cladeId: globals.tree.id } ); }

function scrollToNode( p ) {

    var localGlobal = globals;

    var scaleValue = getScaleValue();

    var node = findClade( { id: p.id, clade: localGlobal.tree  } );
   
    $('#visual_container').scrollTop( ( node.coords.y * scaleValue.y ) - ( config.nodeSeparation.value / 2 ) );  
    $('#visual_container').scrollLeft( ( node.coords[localGlobal.scaling].x * scaleValue.x ) + ( $('#visual_container').width() / 2) );  

    if( !p.fromClick ) { $('#nodeSelector').scrollTop( $('#nodeSelector').scrollTop() + ( $('#' + p.id).position().top - $('#nodeSelector').offset().top ) ); }
}

function redrawCladogram() {

    var localGlobal = globals;
    var localConfig = config;

    localGlobal.pathObjs.main.tree.remove();
    localGlobal.pathObjs.overview.tree.remove();

    localGlobal.pathStrings.main.tree = new StringBuffer();
    localGlobal.pathStrings.overview.tree = new StringBuffer();
   
    //need to write update function so we don't have to redraw
    removeExpandableIcons( { main: true, scaled: true } ); 

    //need to write update function so we don't have to redraw
    clearSupportValueAnnotation();

    initialize(); 

    var scaleValue = getScaleValue();
    
    localGlobal.overviewR = setOverviewCanvas( { global: localGlobal, scaleValue: scaleValue } );

    drawCladogram( { scaleValue: scaleValue, global: localGlobal, config: localConfig } );

    updateSelections( { global: localGlobal } );
    
    $('#draggableLens').offset( { top: $('#scaledTreeContainer').offset().top,
                                  left: $('#scaledTreeContainer').offset().left + $('#scaledTreeContainer').width() - $('#draggableLens').width() } );
    
    $('#visual_container').scrollLeft( localGlobal.canvas.width - $('#visual_container').width() + localGlobal.scrollBarWidth ).scrollTop( 0 );
}

function ajaxRotateChildren( p ) {

    $.ajax( { url: '/' + globals.applicationName + '/default/rotate/' + p.type,
              type: "POST",
              data: { treeId: globals.treeId, cladeId: p.cladeId },
              success: handleTreeResponse } );
}

function handleTreeResponse( newTree ) {

    var localGlobal = globals;

    localGlobal.pathObjs.main.tree.remove();

    localGlobal.tree = eval( "(" + newTree + ")" );
    localGlobal.tree.isRoot = true;
    redrawCladogram();
    reAlignButton();
}

function ajaxLadderize( p ) {

    $.ajax( { url: '/' + globals.applicationName + '/default/ladderize',
              type: "POST",
              data: { treeId: globals.treeId, cladeId: p.cladeId },
              success: handleLadderizeResponse } );
}

function handleLadderizeResponse( newTree ) {

    handleTreeResponse( newTree );
    setLadderizeItem();
}

function saveCladogram() {

    var localGlobal = globals;
    var labels = localGlobal.labelInfo.labels;

    if( localGlobal.cladeButton ) { localGlobal.cladeButton.remove(); }
    localGlobal.cladeButton = null;

    for( var id in labels ) {
        $( '#label' + id ).appendTo(
            ( labels[id].isInternal ) ? $('#internalLabels')
                                      : $('#tipLabels') );
    }

    $.ajax( { url: '/' + globals.applicationName + '/default/save',
              type: "POST",
              data: { svg: escape( document.getElementById('raphaelContainer').innerHTML ) },
              success: handleSaveSVGResponse } );

    for( var id in labels ) { $( '#label' + id ).appendTo( $('#mySVG') ); }
}

function handleSaveSVGResponse( id ) {

    window.open( '/' + globals.applicationName + '/default/receive/svg/' + id );

    removeServerFile( { type: 'svg', id: id } );
}

function saveNewick() {
    
    $.ajax( { url: '/' + globals.applicationName + '/default/getNewick',
              type: "POST",
              data: { treeId: globals.treeId },
              success: handleSaveNewickResponse } );
}

function removeServerFile( p ) {

    $.ajax( { url: '/' + globals.applicationName + '/default/remove',
              type: "POST",
              data: { id: p.id, type: p.type } } );

}

function handleSaveNewickResponse( id ) {
    
    window.open( '/' + globals.applicationName + '/default/receive/newick/' + id );
    
    removeServerFile( { type: 'newick', id: id } );
}

function alignLens() {
    
    var overviewScale = globals.overviewScale;
    var scaleValue = getScaleValue();

    $('#draggableLens').css( { top: ( ( $('#visual_container').scrollTop() * overviewScale.y / scaleValue.y ) + $('#scaledTreeContainer').position().top ),
                               left: ( $('#visual_container').scrollLeft() * overviewScale.x / scaleValue.x ) + $('#scaledTreeContainer').position().left } );
}

function handleScroll(e) {

    if( globals.currentEvents.movingLens || isContextMenu() ) { return; }
    
    var lastMouseMove = globals.lastMouseMove;

    if( ! $('#dragger').length ) { alignLens(); }

    moveButton( { pageX: lastMouseMove.x, pageY: lastMouseMove.y } );
}

function getScaleValue() {

    if( $('#horiSlider').length == 0 ) { return { x: 1, y: 1 }; }

    var horiScaleValue = $("#horiSlider").slider("option", "value"); if( horiScaleValue == 0 ) { horiScaleValue = 1; }
    var vertScaleValue = $("#vertSlider").slider("option", "value"); if( vertScaleValue == 0 ) { vertScaleValue = 1; }

    return { x: horiScaleValue / 100,  y: vertScaleValue / 100 };
}

function handleSlide() {

    var localGlobal = globals;
    var localConfig = config;

    var scaleValue = getScaleValue();
    
    var treeWidth = ( localConfig.generationSeparation.value * ( localGlobal.treeInfo.generations - 1 ) );

    setCanvasSize( { canvas: localGlobal.r, treeWidth: treeWidth, global: localGlobal, config: localConfig, container: 'raphaelContainer', scaleValue: scaleValue } );

    var raphaelContainerDims = { height: $('#raphaelContainer').height(), width: $('#raphaelContainer').width() };

    var visualContainerDims = { height: $('#visual_container').height(), width: $('#visual_container').width() };

    redrawCladogram();

    if( (raphaelContainerDims.height < visualContainerDims.height) &&
        (raphaelContainerDims.width < visualContainerDims.width) &&
        $('#visual_container').hasClass('scrollable') ) { 
                    
            setTimeout( function() { $('#visual_container').removeClass('scrollable'); } , 1000 );
                    
    } else if( ! $('#visual_container').hasClass('scrollable') ) { $('#visual_container').addClass('scrollable'); }
    
    setLensSize();

    reAlignButton();
}

function handleDrag() {

    var lensCoords = $('#draggableLens').offset();
    var containerCoords = $('#scaledTreeContainer').offset();

    var difference = { y: lensCoords.top - containerCoords.top,
                       x: lensCoords.left - containerCoords.left };

    if( difference.y == 1 ) { difference.y = 0; }
    if( difference.x == 1 ) { difference.x = 0; }

    if( $('#scaledTreeContainer').height() - difference.y - $('#draggableLens').height() == 1 ) { difference.y = difference.y + 2; }
    if( $('#scaledTreeContainer').width() - difference.x - $('#draggableLens').width() == 1 ) { difference.x = difference.x + 2; }

    $('#visual_container').scrollTop( difference.y / globals.overviewScale.y );
    $('#visual_container').scrollLeft( difference.x / globals.overviewScale.x );
}

function setDragging() { globals.currentEvents.movingLens = true; }
function unsetDragging() { globals.currentEvents.movingLens = false; }


function redrawOverview() {

    var localGlobal = globals;

    localGlobal.overviewR = setOverviewCanvas( { global: localGlobal, scaleValue: getScaleValue } );
    
    setLensSize();

    var pathStrings = { tree: new StringBuffer(),
                        supportValue: new StringBuffer() };

    updateOverviewTreePath( { pathStrings: pathStrings, clade: localGlobal.tree, supportValueThreshold: localGlobal.supportValueInfo.threshold,
                              overviewScale: localGlobal.overviewScale, scaling: localGlobal.scaling } );
    
    localGlobal.pathObjs.overview.tree =
        createTreePath( { string: pathStrings.tree.toString(),
                          canvas: localGlobal.overviewR,
                          attr: { stroke: config.cladeColor.value,
                                  "stroke-width": .5 } } );
   
    if( localGlobal.supportValueInfo.thickness ) { 
        localGlobal.pathObjs.overview.supportValue =
            createTreePath( { string: pathStrings.supportValue.toString(),
                              canvas: localGlobal.overviewR,
                              attr: { stroke: config.cladeColor.value,
                                      "stroke-width": localGlobal.supportValueInfo.thickness * localGlobal.overviewScale.y  } } );
    }
}

function updateOverviewTreePath( p ) {

    var X = p.clade.coords[p.scaling].x;
    var DX = p.clade.coords[p.scaling].dx;
    var Y = p.clade.coords.y;

    p.pathStrings.tree.append(
        ( p.clade.isRoot ) ? "M" + ( X * p.overviewScale.x ) + " " + ( Y * p.overviewScale.y ) +
                             "l" + ( -20 * p.overviewScale.x ) + " 0" 

                           : "M" + ( X * p.overviewScale.x ) + " " + ( Y * p.overviewScale.y ) +
                             "l" + ( DX * p.overviewScale.x ) + " 0" ); 

    if( parseInt( p.clade.name ) > p.supportValueThreshold ) {
        p.pathStrings.supportValue.append( "M" + ( X * p.overviewScale.x ) + " " + ( ( Y + ( .25 ) ) * p.overviewScale.y ) +
                                           "l" + ( DX * p.overviewScale.x ) + " 0 " );
    }

    var childCount = p.clade.children.length;

    if( childCount ) {

        var firstChildY = p.clade.children[0].coords.y;
        var lastChildY = p.clade.children[ childCount-1 ].coords.y;

        p.pathStrings.tree.append(
            "M" + ( X * p.overviewScale.x ) + " " + ( firstChildY * p.overviewScale.y ) +
            "L" + ( X * p.overviewScale.x ) + " " + ( lastChildY * p.overviewScale.y ) );

        for( var i = 0; i < childCount; i++ ) {
            updateOverviewTreePath( { pathStrings: p.pathStrings, clade: p.clade.children[i], supportValueThreshold: p.supportValueThreshold,
                                      overviewScale: p.overviewScale, scaling: p.scaling } );
        }
    }
}

function getCladeSelectionString( p ) {
 
    addNodeToTreePath( { pathStrings: p.pathStrings,
                         clade: p.clade,
                         scaling: p.scaling,
                         scaleValue: p.scaleValue,
                         overviewScale: p.overviewScale } );

    for( var i = 0, ii = p.clade.children.length; i < ii; i++ ) {

        getCladeSelectionString( { pathStrings: p.pathStrings,
                                   clade: p.clade.children[i],
                                   scaling: p.scaling,
                                   scaleValue: p.scaleValue,
                                   overviewScale: p.overviewScale } );
    }
}

function updateSelections( p ) {

    var selectedClades = p.global.selectedClades;
     
    for( var id in selectedClades.main ) {

        var clade = findClade( { id: id, clade: p.global.tree } );
        
        if( clade ) {
            
            var pathStrings = { main: new StringBuffer(), overview: new StringBuffer() };

            getCladeSelectionString( { clade: clade,
                                       scaling: p.global.scaling,
                                       scaleValue: getScaleValue(),
                                       overviewScale: p.global.overviewScale,
                                       pathStrings: pathStrings } );

            selectedClades.main[id].attr( { path: pathStrings.main.toString() } ).toFront();
            selectedClades.scaled[id].attr( { path: pathStrings.overview.toString() } ).toFront();
            
        } else {

            selectedClades.main[id].hide().remove();
            delete selectedClades.main[id];

            selectedClades.scaled[id].hide().remove();
            delete selectedClades.scaled[id];
        }
    }

}


function setLadderizeItem() {
    if( globals.tree.isLadderized == 'True' ) { $('#ladderizeItem').text('Reverse Ladderize'); }
    else { $('#ladderizeItem').text('Ladderize'); }
}

function pruneTaxa() {

    var localGlobal = globals;
    var selectedClades = globals.selectedClades.main;

    if( selectedClades[ localGlobal.tree.id ] && selectedClades[ localGLobal.tree.id ].isVisible ) { return; }

    var idsToPrune = [ ];
    
    for ( var id in selectedClades ) { if( selectedClades[id].isVisible ) { idsToPrune.push( id ); } }
    
    var tipWithoutLabel = searchForTipWithoutLabel( { idsToPrune: idsToPrune, clade: localGlobal.tree } );

    if( tipWithoutLabel ) { shadowBoxTipPrompt( { id: tipWithoutLabel, nodeIds: idsToPrune.join() } ); }
    else { ajaxPrune( { treeId: localGlobal.treeId, nodeIds: idsToPrune.join() } ); }
}

function searchForTipWithoutLabel( p ) {

    var id = p.clade.id;
        
    for( var i = 0, ii = p.idsToPrune.length; i < ii; i++ ) {

        if( id == p.idsToPrune[i] ) { return false; }
    }

    var rv = true;

    var childCount = p.clade.children.length;

    if( childCount == 0 ) { return false; }

    if( ! p.clade.name ) {

        var truthArray = [];

        for( var i = 0, ii = childCount; i < ii; i++ ) {

            var child = p.clade.children[i];

            var found = false;

            for( var j = 0, jj = p.idsToPrune.length; j < jj; j++ ) {

                if( child.id == p.idsToPrune[j] ) { found = true; }
            }

            truthArray.push( found );
        }

        for( var i = 0, ii = childCount; i < ii; i++ ) {

            if( !truthArray[i] ) { rv = false; }
        }

    } else {

        rv = false;
    }

    if( rv ) {

        return id;

    } else {

        for( var i = 0, ii = p.clade.children.length; i < ii; i++ ) {

            rv = searchForTipWithoutLabel( { idsToPrune: p.idsToPrune, clade: p.clade.children[i] } );

            if( rv ) return rv;
        }
    }

    return false;
}

function ajaxPrune( p ) {

    $.ajax( { url: '/' + globals.applicationName + '/default/prune',
              type: "POST",
              data: { treeId: p.treeId, nodeIds: p.nodeIds },
              success: handleTreeResponse } );
}


function getMRCA( p ) {

    var childCount = p.clade.children.length;
    if( childCount == 0 ) { return p.commonAncestorId; }

    var rv;

    var idCount = p.ids.length;

    var truthArray = [];

    for( var i = 0, ii = idCount; i < ii; i++ ) {
        
        truthArray.push( ( isAncestor( { clade: p.clade, id: p.ids[i], first: true } ) || p.clade.id == p.ids[i] ) );
    }

    var isCommonAncestor = true;

    for( var i = 0, ii = idCount; i < ii; i++ ) { if( !truthArray[i] ) { isCommonAncestor = false; } }

    if( isCommonAncestor ) {

        p.commonAncestorId = p.clade.id;

        for( var i = 0, ii = childCount; i < ii; i++ ) {
            
            var result = getMRCA( { clade: p.clade.children[i], commonAncestorId: p.commonAncestorId, ids: p.ids } );
            
            if( result != p.commonAncestorId ) { return result; }
        }
        
        return p.commonAncestorId;

    } else {

        return p.commonAncestorId;
    }
}

function mrca() {

    var localGlobal = globals;
    var selectedClades = localGlobal.selectedClades.main;

    var selectedIds = [];

    for( var id in selectedClades ) { if( selectedClades[id].isVisible ) { selectedIds.push( id ); } }

    var mrcaId = getMRCA( { ids: selectedIds, commonAncestorId: undefined, clade: localGlobal.tree } );

    if( mrcaId == globals.tree.id ) { return; }
    
    $.ajax( { url: '/' + globals.applicationName + '/default/mrca',
              type: "POST",
              data: { treeId: localGlobal.treeId, nodeId: mrcaId },
              success: handleTreeResponse } );
}

function isAncestor( p ) {

    if( p.clade.id == p.id && ( ! p.first ) ) { return true; }

    else {

        for( var i = 0, ii = p.clade.children.length; i < ii; i++ ) {

            rv = isAncestor( { clade: p.clade.children[i], id: p.id } );
            
            if( rv ) { return true; }

        }
    }

    return false;
}


function handleMouseMove( e ) {

    if( isContextMenu() ) { return; }
    
    var localGlobal = globals;

    localGlobal.lastMouseMove = { x: e.pageX, y: e.pageY };

    if( localGlobal.currentEvents.mouseDown ) { handleMouseDrag(e); } else { moveButton(e); }
}


function handleMouseDrag(e) {

    var draggerOrigin = globals.draggerOrigin;

    if( $('#dragger').length == 0 ) {

        $('#visual_container').prepend(
            makeEl('div').attr( { 'id': 'dragger', 'class': 'draggerBox' } )
                         .css( { left: e.pageX, top: e.pageY, position: 'absolute', zIndex: 500, opacity: .3 } ) );

        draggerOrigin.x = e.pageX;
        draggerOrigin.y = e.pageY;

    } else {

        var draggerEl = $('#dragger');

        var containerOffset = $('#visual_container').offset();
        var containerWidth = $('#visual_container').width();
        var containerHeight = $('#visual_container').height();

        if( e.pageX > ( containerOffset.left + containerWidth ) ) { e.pageX = containerOffset.left + containerWidth; }

        if( e.pageX < containerOffset.left ) { e.pageX = containerOffset.left; }

        if( e.pageY > ( containerOffset.top + containerHeight ) ) {  e.pageY = containerOffset.top + containerHeight; }

        if( e.pageY < containerOffset.top ) { e.pageY = containerOffset.top; }

        var width = e.pageX - draggerOrigin.x;
        var height = e.pageY - draggerOrigin.y;

        draggerEl.width( Math.abs(width) ).height( Math.abs(height) );

        if( width < 0 ) { draggerEl.css( { marginLeft: width } ); } else { draggerEl.css( { marginLeft: 0 } ); }
        if( height < 0 ) { draggerEl.css( { marginTop: height } ); } else { draggerEl.css( { marginTop: 0 } ); }
    }
}

function moveButton(e) {

    var localGlobal = globals;
    var localConfig = config;
    
    var scaleValue = getScaleValue();

    var containerElement = $('#visual_container');
    var containerElementOffset = $('#visual_container').offset();

    var closestClade =
        findClosestClade(
            { x: e.pageX, y: e.pageY, closestClade: undefined, currentDistance: 1000000,
              scaleValue: scaleValue, scaling: localGlobal.scaling, clade: localGlobal.tree,
              offset: { y: containerElementOffset.top - containerElement.scrollTop(),
                        x: containerElementOffset.left - containerElement.scrollLeft() } } ).clade;

    var buttonCoords = { x: closestClade.coords[ localGlobal.scaling ].x * scaleValue.x,
                         y: closestClade.coords.y * scaleValue.y,
                         radius: localConfig.buttonRadius.value };
                         
    if( localGlobal.cladeButton ) {

        if( closestClade.id == localGlobal.cladeButton.id ) { return; }

        var oldCoords = localGlobal.cladeButton.getBBox();

        localGlobal.cladeButton.translate( buttonCoords.x - oldCoords.x - buttonCoords.radius,
                                           buttonCoords.y - oldCoords.y - buttonCoords.radius ).toFront();

    } else {

        localGlobal.cladeButton =
            localGlobal.r.circle( buttonCoords.x, buttonCoords.y, buttonCoords.radius )
                            .attr( { fill: localConfig.buttonColor.value,
                                     stroke: localConfig.buttonColor.value,
                                     "fill-opacity": .7,
                                     "stroke-width": 2 } ).click( cladeButtonOnClick );


    }

    localGlobal.cladeButton.id = closestClade.id;
    localGlobal.cladeButton.expandable = ( closestClade.isExpandable == 'True' ) ? true : false;
}

function cladeButtonOnClick() { selectDescendants( { id: globals.cladeButton.id } ); }

function highlightCladeLabelSelectors( p ) {

    var elToHighlight = $('#' + p.clade.id);

    if( elToHighlight.length && ( ! elToHighlight.hasClass('highlight') ) ) { elToHighlight.addClass('highlight'); }

    for( var i = 0, ii = p.clade.children.length; i < ii; i++) { highlightCladeLabelSelectors( { clade: p.clade.children[i] } ); }
}

function disableLabelSelectorActions() {

    var reRootEl = $('#reRootLink');
    var pruneEl = $('#pruneLink');

    if( ! reRootEl.hasClass('disabled') ) { reRootEl.addClass('disabled').unbind('click'); }
    if( ! pruneEl.hasClass('disabled') ) { pruneEl.addClass('disabled').unbind('click'); }
}

function enableLabelSelectorActions() {
    
    var reRootEl = $('#reRootLink');
    var pruneEl = $('#pruneLink');

    if( reRootEl.hasClass('disabled') ) { reRootEl.removeClass('disabled').unbind('click').click( function() { mrca(); } ); }
    if( pruneEl.hasClass('disabled') ) { pruneEl.removeClass('disabled').unbind('click').click( function() { pruneTaxa(); } ); }
}

function selectDescendants( p ) {

    var localGlobal = globals;
    var selectedClades = localGlobal.selectedClades;

    var scaleValue = getScaleValue();
            
    highlightCladeLabelSelectors( { clade: findClade( { id: p.id, clade: localGlobal.tree } )} );

    enableLabelSelectorActions();

    if( selectedClades.main[ p.id ] ) {
        
        if( ! selectedClades.main[ p.id ].isVisible ) {

            selectedClades.main[ p.id ].show().toFront().attr( { stroke: localGlobal.selectedCladeColor } );
            selectedClades.scaled[ p.id ].show().toFront().attr( { stroke: localGlobal.selectedCladeColor } );

            selectedClades.main[ p.id ].isVisible = true;
        }
        
        return;
    }
    
    var pathStrings = { main: new StringBuffer(),
                        overview: new StringBuffer() };


    getCladeSelectionString( { clade: findClade( { id: p.id, clade: localGlobal.tree } ),
                               scaling: localGlobal.scaling,
                               scaleValue: getScaleValue(),
                               overviewScale: localGlobal.overviewScale,
                               pathStrings: pathStrings } );

    selectedClades.main[ p.id ] =
        createTreePath( { string: pathStrings.main.toString(),
                          canvas: localGlobal.r,
                          attr: { stroke: localGlobal.selectedCladeColor,
                                  "stroke-width": config.pathWidth.value * scaleValue.y } } );

    selectedClades.scaled[ p.id ] =
        createTreePath( { string: pathStrings.overview.toString(),
                          canvas: localGlobal.overviewR,
                          attr: { stroke: localGlobal.selectedCladeColor,
                                  "stroke-width": .5 } } );

    selectedClades.main[ p.id ].isVisible = true;
}

function clearSelection() {

    var localGlobal = globals;
    var selectedClades = localGlobal.selectedClades;

    for( var id in selectedClades.main ) {

        selectedClades.main[id].hide();
        selectedClades.main[id].isVisible = false;
        selectedClades.scaled[id].hide();

        removeCladeLabelSelectors( { clade: findClade( { id: id, clade: localGlobal.tree } ) } );
    }

    disableLabelSelectorActions(); 
}


function removeCladeLabelSelectors( p ) {

    var el = $('#' + p.clade.id);

    if( el.length && el.hasClass('highlight') ) { el.removeClass('highlight'); }

    for( var i = 0, ii = p.clade.children.length; i < ii; i++ ) {

        removeCladeLabelSelectors( { clade: p.clade.children[i] } );
    }
}


function dragSelectClades( p ) {

    var draggerOffset = p.dragger.offset();

    var containerElement = $('#visual_container');
    var containerElementOffset = containerElement.offset();

    var draggerBox = { x1: draggerOffset.left, x2: draggerOffset.left + p.dragger.width(),
                       y1: draggerOffset.top, y2: draggerOffset.top + p.dragger.height() };


    var selectedIds = [];

    retrieveDraggerSelections( { draggerBox: draggerBox,
                                 offset: { y: containerElementOffset.top - containerElement.scrollTop(),
                                           x: containerElementOffset.left - containerElement.scrollLeft() },
                                 selectedIds: selectedIds,
                                 scaling: p.global.scaling,
                                 scaleValue: getScaleValue(),
                                 clade: p.global.tree } );

    if( selectedIds.length ) {

        var selectionCladeId;

        if( selectedIds.length == 1 ) { selectionCladeId = selectedIds[0]; }

        else { selectionCladeId = getMRCA( { ids: selectedIds, commonAncestorId: undefined, clade: p.global.tree } ); }

        selectDescendants( { id: selectionCladeId } );
    }
}

function retrieveDraggerSelections( p ) {

    var cladeCoords = p.clade.coords;

    var relativeCladeX = ( cladeCoords[ p.scaling ].x * p.scaleValue.x ) + p.offset.x;

    var relativeNodePath = { x2: relativeCladeX,
                             x1: relativeCladeX + ( cladeCoords[ p.scaling ].dx * p.scaleValue.x ),
                             y: ( cladeCoords.y * p.scaleValue.y ) + p.offset.y };

    if( ( relativeNodePath.y > p.draggerBox.y1 && relativeNodePath.y < p.draggerBox.y2 ) &&

        ( ( p.draggerBox.x1 > relativeNodePath.x1 && p.draggerBox.x1 < relativeNodePath.x2 ) ||

          ( p.draggerBox.x2 > relativeNodePath.x1 && p.draggerBox.x2 < relativeNodePath.x2 ) ||

          ( relativeNodePath.x2 > p.draggerBox.x1 && relativeNodePath.x2 < p.draggerBox.x2 ) || 

          ( relativeNodePath.x1 > p.draggerBox.x1 && relativeNodePath.x1 < p.draggerBox.x2 ) ) ) {

                p.selectedIds.push( p.clade.id );
        }

        var childCount = p.clade.children.length;

        if( childCount ) {

            var recursiveParams = { draggerBox: p.draggerBox,
                                    offset: p.offset,
                                    selectedIds: p.selectedIds,
                                    scaling: p.scaling,
                                    scaleValue: p.scaleValue };

            var firstChildRelativeY =  ( p.clade.children[0].coords.y * p.scaleValue.y ) + p.offset.y;
            var lastChildRelativeY =  ( p.clade.children[ childCount - 1].coords.y * p.scaleValue.y ) + p.offset.y;

            if( p.draggerBox.y2 < firstChildRelativeY ) {
                recursiveParams.clade = p.clade.children[0];
                retrieveDraggerSelections( recursiveParams );
            }

            else if( p.draggerBox.y1 > lastChildRelativeY ) {
                recursiveParams.clade = p.clade.children[ childCount - 1 ];
                retrieveDraggerSelections( recursiveParams );
            }

            else { for( var i = 0, ii = childCount; i < ii; i++ ) {
                recursiveParams.clade = p.clade.children[ i ];
                retrieveDraggerSelections( recursiveParams );
            }
        }
    }
}

function handleRightClick( p ) {

    var cladeId = p.global.cladeButton.id;

    var container = $('#visual_container');
    var containerOffset = container.offset();

    var label = p.global.labelInfo.labels[ cladeId ];

    var clade = findClade( { id: cladeId, clade: p.global.tree } );

    var contextMenu = makeEl('ul').attr( { 'id': 'contextMenu', 'class': 'contextMenu' } )
                                  .css( { left: p.x + 2, top: p.y + 2 } );
   
    var labelDialogue = ( label ) ? 'Edit Label' : 'Add Label'

    var menuItems = [ { text: ( label ) ? 'Edit Label' : 'Add Label',
                        func: contextEditLabelOnClick,
                        params:  { id: cladeId, text: ( label ) ? label.attr('text') : '' } } ];

    if( clade.isExpandable == 'True' ) {
        
        menuItems.push( { text: 'Expand Clade',
                          func: contextExpandCladeOnClick,
                          params: { id: cladeId } } );

    } else if( clade.children.length ) {

        menuItems.push( { text: 'Collpase Clade',
                          func: contextCollapseCladeOnClick,
                          params: { id: cladeId } },
                            
                        { text: ( clade.isLadderized == 'True') ? 'Reverse Ladderize' : 'Ladderize',
                          func: contextLadderizeCladeOnClick,
                          params: { cladeId: cladeId } },

                        { text: 'Rotate Children Forward',
                          func: contextRotateChildren,
                          params: { cladeId: cladeId, type: 'forward' } },
                        
                        { text: 'Rotate Children Reverse',
                          func: contextRotateChildren,
                          params: { cladeId: cladeId, type: 'reverse' } },
                         
                        { text: 'Save Clade for Grafting',
                          func: showSaveCladeDialogue,
                          params: { cladeId: cladeId } } );
    }

    for( var i = 0, ii = menuItems.length; i < ii; i++ ) {

        contextMenu.append( makeEl( 'li' ).mouseover( addContextHighlight )
                                          .mouseout( removeContextHighlight )
                                          .bind( 'click', menuItems[i].params, menuItems[i].func )
                                          .bind( 'click', contextMenu, closeContextMenu )
                                          .text( menuItems[i].text ) );
    }

    container.append( contextMenu );
}

function saveCladeForGrafting( p ) { 

    var localGlobal = globals;

    var name = $('#cladeName').val();
    if( name.trim() == '' ) { name = 'Untitled Tree Piece'; }

    var comment = $('#cladeComment').val();
    if( comment.trim() == '' ) { comment = getCurrentDateTime() }

    $.ajax( { url: '/' + localGlobal.applicationName + '/default/saveClade',
              type: "POST",
              data: { nodeId: p.data.id, name: name  },
              success: handleSaveCladeSuccess,
              error: handleSaveCladeError } );
}

function handleSaveCladeSuccess() { alert('Clade Saved'); }
function handleSaveCladeError() { alert('Failed to Save Clade'); }

function contextCollapseCladeOnClick( p ) { collapseClade( p.data ); }
function contextLadderizeCladeOnClick( p ) { ajaxLadderize( p.data ); }
function contextRotateChildren( p ) { ajaxRotateChildren( p.data ); }
function contextEditLabelOnClick( p ) { showEditLabel( p.data ); }
function removeContextMenu() { $('#contextMenu').empty().remove(); }
function contextExpandCladeOnClick( p ) { ajaxExpandClade( p.data ); }


function hideLeftPanelOnClick() {

    $('#leftPanel').hide('slow', hideLeftPanel );
}

function hideLeftPanel() {

    var container = $('#visual_container');
    
    var containerOffset = container.offset();

    container.removeClass('width78').addClass('fullWidth');

    alignSliders( { mainPanelOffset: containerOffset } );
        
    container.append( makeEl('a').attr( { 'class': 'smallText blue',
                                          'id': 'showLeftPanelLink',
                                          'href': 'javascript:void(0)' } )
                                 .css( { position: 'absolute',
                                         zIndex: 500,
                                         left: containerOffset.left + container.width() - 125,
                                         top: containerOffset.top + 5 } )
                                 .text('Show Left Panel')
                                 .click( showLeftPanelOnClick ) );
}

function disableUntilAllLabelsCreated() {

    $('#horiSlider').slider( 'option', 'disabled', true );
    $('#vertSlider').slider( 'option', 'disabled', true );

    $('#tabs').tabs( 'option', 'disabled', [ 1, 2 ] );

    $.each( [ $('#leftPanel > .panelItem') ], function( index, item ) { $(item).unbind('click').removeClass('panelItem').addClass('disabledPanelItem'); } );
}


function createProgressBar( p ) {

    disableUntilAllLabelsCreated();

    var containerOffset = p.container.offset();
    var containerWidth = p.container.width();

    var progressBarWidth = containerWidth * .10;
    var progressBarLeft = containerOffset.left + (containerWidth/2) - (containerWidth*.05);

    p.container.append(
        makeEl('div').attr( { 'id': 'labelProgressBarHeader', 'class': 'progressBarHeader' } )
                     .css( { width: progressBarWidth,
                             left: progressBarLeft,
                             top: containerOffset.top + 15 } ).addClass('smallText').text('Adding Labels'),

        makeEl('div').attr( { 'id': 'labelProgressBar', 'class': 'progressBar' } )
                     .css( { width: progressBarWidth,
                             left: progressBarLeft,
                             top: containerOffset.top + 30 } ) );
        
    $('#labelProgressBar').progressbar();

}

function alignSliders( p ) {

    $('#vertSlider').css( { left: p.mainPanelOffset.left + 15,
                            top: p.mainPanelOffset.top + 20 } );
        
    $('#horiSlider').css( { left: p.mainPanelOffset.left + 35,
                            top: p.mainPanelOffset.top + 20 } );
}

function showLeftPanelOnClick() {

    $('#leftPanel').show('slow', showLeftPanel );
}

function showLeftPanel() {
        
    var container = $('#visual_container');
    
    container.removeClass('fullWidth').addClass('width78');

    var containerOffset = container.offset();

    alignSliders( { mainPanelOffset: containerOffset } );

    $('#showLeftPanelLink').empty().remove();
}

function zoomFull() {

    var container = $('#visual_container');

    if( ! container.hasClass('scrollable') ) { setTimeout( makeContainerScrollable, 1000, container ); }

    $('#vertSlider').slider( "option", "value", 100 );
    $('#horiSlider').slider( "option", "value", 100 );

    handleSlide();
}

function makeContainerScrollable( container ) { container.addClass('scrollable'); }


function fitToScreen() {

    var scrollBarWidth = globals.scrollBarWidth;

    var mainPanel = $('#visual_container');
    var canvasContainer = $('#raphaelContainer');

    var diff = { height: ( ( mainPanel.height() ) / canvasContainer.height() ),
                 width: ( ( mainPanel.width() ) / canvasContainer.width() ) };

    if( diff.height >= 1 && diff.width >= 1 ) { return; }
    if( diff.height > 1 ) { diff.height = 1; }  if( diff.width > 1 ) { diff.width = 1; } 

    if( mainPanel.hasClass('scrollable') ) { setTimeout( removeScrollable, 1000, { mainPanel: mainPanel } ); }

    $('#vertSlider').slider( "option", "value", diff.height * 100 );
    $('#horiSlider').slider( "option", "value", diff.width * 100 );

    handleSlide();
}

function removeScrollable( p ) { p.mainPanel.removeClass('scrollable'); }

function showZoomOptions() {

    var zoomOptionsInfo = { panelItem: 'zoomItem',
                            options: [ { text: 'Fit To Screen', func: fitToScreen },
                                       { text: '100 %', func: zoomFull } ] };

    createPanelItemOptionsPopup( zoomOptionsInfo );
}

function showExportOptions() {

    var exportOptionsInfo = { panelItem: 'exportItem',
                              options: [ { text: 'SVG', func: saveCladogram },
                                         { text: 'Newick', func: saveNewick } ] };

    createPanelItemOptionsPopup( exportOptionsInfo );
}

function showLabelOptions() {

    var allLabelsText = ( globals.labelInfo.visible.all ) ? 'Hide All Labels' : 'Show All Labels';
    var tipLabelsText = ( globals.labelInfo.visible.tips ) ? 'Hide Tip Labels' : 'Show Tip Labels';
    var internalLabelsText = ( globals.labelInfo.visible.internal ) ? 'Hide Internal Labels' : 'Show Internal Labels';

    var labelOptionsInfo = { panelItem: 'labelItem',
                             options: [ { text: allLabelsText,
                                          func: function() { updateLabels( { text: allLabelsText } ); updateLabelInfo( { type: 'all' } ); } },
                                        { text: tipLabelsText,
                                          func: function() { updateLabels( { text: tipLabelsText } ); updateLabelInfo( { type: 'tip' } ); } },
                                        { text: internalLabelsText,
                                          func: function() { updateLabels( { text: internalLabelsText } ); updateLabelInfo( { type: 'internal' } ); } } ] };

    createPanelItemOptionsPopup( labelOptionsInfo );
}

function showNodeSelectorOptions() {
    
    var numericEntryInfo = ( globals.numericEntriesVisible ) ? { text: 'Hide Numeric Entries', fun: hideNumericEntries }
                                                             : { text: 'Show Numeric Entries', fun: showNumericEntries };

    var nodeSelectorOptionsInfo = { panelItem: 'labelSelectorOptions',
                                    options: [ { text: ( globals.topologicalSort ) ? 'Alphabetize' : 'Topological Sort', func: labelSelectorSortOnClick },
                                               { text: numericEntryInfo.text, func: numericEntryInfo.fun } ] };

    createPanelItemOptionsPopup( nodeSelectorOptionsInfo );
}

function labelSelectorSortOnClick() {

    var localGlobal = globals;

    localGlobal.topologicalSort = ( localGlobal.topologicalSort ) ? false : true;

    sortLabelSelectorRows();
}

function updateNodeSuggest( p ) {

    var nodeSuggestTextBox = makeEl('input').attr( { type: 'text', id: 'nodeSuggest' } );

    $('#nodeSuggestDiv').empty().append( nodeSuggestTextBox );

    nodeSuggestTextBox.autoSuggest(
        p.labels, { selectedItemProp: "text",
                    searchObjProps: "text",
                    startText: "",
                    resultsHighlight: false,
                    keyDelay: 150,
                    resultClick: autoSuggestionOnClick } );

    $('#nodeSuggest > li').addClass('fullWidth');
    $('#nodeSuggest > input').width( $('#nodeSuggest').width() );
}

function autoSuggestionOnClick( data ) {

    var labelId = data.attributes.id;

    toggleHighlight( { id: labelId } );
    
    $('.as-close').click();
    
    if( $('#' + labelId).hasClass('highlight') ) { scrollToNode( { id: labelId } ); }
}

function hideNumericEntries() {

    var localGlobal = globals;
    var labels = localGlobal.labelInfo.labels;

    var keepVisible = [];
    
    for( var id in labels ) {

        if( labels[id].attr('text').match(/^\d+$/) ) {
            
            $('#'+labels[id].node.id.substring(5)).hide();

        } else {

            keepVisible.push( labels[id] );
        }
    }

    localGlobal.numericEntriesVisible = false;

    updateNodeSuggest( { labels: keepVisible } );

}

function showNumericEntries() {
    
    var localGlobal = globals;
    var labels = localGlobal.labelInfo.labels;

    var keepVisible = [];

    for( var id in labels ) {

        keepVisible.push( labels[id] );
        
        if( labels[id].attr('text').match(/^\d+$/) ) { $('#'+labels[id].node.id.substring(5)).show(); }
    }

    localGlobal.numericEntriesVisible = true;

    updateNodeSuggest( { labels: keepVisible } );
}

function ajaxUpdateScaling( p ) {

    var localGlobal = globals;

    $.ajax( { url: '/' + localGlobal.applicationName + '/default/updateScaling',
              type: "POST",
              data: { treeId: localGlobal.treeId, scaling: p.type },
              success: handleScalingResponse } );
}

function handleScalingResponse( scaling ) {

    var localGlobal = globals;
     
    localGlobal.pathObjs.main.tree.remove();

    localGlobal.scaling = scaling;

    redrawCladogram();

    reAlignButton();
}

function createPanelItemOptionsPopup( p ) {

    var panelItemOffset = $('#' + p.panelItem).offset();

    var popup = makeEl('ul').attr( { 'id': 'panelItemOptionsPopup', 'class': 'panelItemPopup' } )
                            .css( { position: 'absolute', zIndex: 600, left: panelItemOffset.left + $('#'+p.panelItem).width() - 5,
                                    top: panelItemOffset.top + 5 } );
                            

    for( var i = 0, ii = p.options.length; i < ii; i++ ) {

        popup.append( makeEl('li').attr( { 'class': 'panelItemPopupItem' } )
                                  .text( p.options[i].text )
                                  .click( p.options[i].func ).click( closePanelItemOptionsPopup ) );
    }

    $('#leftPanel').prepend( popup );
}

function closePanelItemOptionsPopup() { $('#panelItemOptionsPopup').empty().remove(); }


function smoothScaling() { ajaxUpdateScaling( { type: 'smooth' } ); }
function normalizedScaling() { ajaxUpdateScaling( { type: 'unscaled' } ); }
function scaledScaling() { ajaxUpdateScaling( { type: 'scaled' } ); }

function showScalingOptions() {

    var scalingOptionsInfo = { panelItem: 'scaleItem',
                               options: [ { text: 'Smooth', func: smoothScaling },
                                          { text: 'Normalized', func: normalizedScaling } ] };


    if( globals.scaledBranches ) { scalingOptionsInfo.options.push( { text: 'Scaled', func: scaledScaling } ); }

    createPanelItemOptionsPopup( scalingOptionsInfo );
}

function updateLabels( p ) {

    var labels = globals.labelInfo.labels;

    var showHideFun = ( p.text.indexOf('Show') > -1 ) ? showLabel : hideLabel;
    
    var labelFilterFun =
        ( p.text.indexOf('All') > -1 ) ? filterNoLabels
      : ( p.text.indexOf('Tip') > -1 ) ? filterOutInternal
      : filterOutTips;
    
    for( var id in labels ) { if( labelFilterFun( labels[id] ) ) { showHideFun( labels[id] ); } }
}

function showLabel( label ) { label.show(); }
function hideLabel( label ) { label.hide(); }
function filterNoLabels( label ) { return true; }
function filterOutInternal( label ) { return !label.isInternal; }
function filterOutTips( label ) { return label.isInternal; }

function updateLabelInfo( p ) {

    var visibilityInfo = globals.labelInfo.visible;

    if( p.type == 'all' ) { visibilityInfo.all = ( visibilityInfo.all ) ? false : true; }
    else if( p.type == 'tip' ) { visibilityInfo.tips = ( visibilityInfo.tips ) ? false : true; }
    else if( p.type == 'internal' ) { visibilityInfo.internal = ( visibilityInfo.internal ) ? false : true; }

}

function ajaxExpandClade( p ) {

    var localGlobal = globals;

    $.ajax( { url: '/' + localGlobal.applicationName + '/default/expandClade',
          type: "POST",
          data: { cladeId: p.id, maxTips: config.maxTips.value, treeId: localGlobal.treeId },
          success: handleTreeResponse } );
}

function collapseClade( p ) {

    var localGlobal = globals;

    $.ajax( { url: '/' + localGlobal.applicationName + '/default/collapseClade',
              type: "POST",
              data: { cladeId: p.id, treeId: localGlobal.treeId },
              success: handleTreeResponse } );
}

function alignHideButton( p ) {
    
    $('#hideButton').css( { position: 'absolute',
                            left: p.mainPanelOffset.left - 30,
                            top: p.mainPanelOffset + 5 } );
}

function resizeTabs() {

    var tabHeight = $('#visual_container').height() - $('#tabs').offset().top;
    var fragmentHeight = tabHeight - $('#tabSelector').height();

    $('#tabs').height( tabHeight );

    $('#fragment-1').height( fragmentHeight );
    $('#fragment-2').height( fragmentHeight );
    $('#fragment-3').height( fragmentHeight );
}


function handleWindowResize() {
    
    var mainPanelOffset = $('#visual_container').offset();

    alignSliders( { mainPanelOffset: mainPanelOffset } );

    alignHideButton( { mainPanelOffset: mainPanelOffset } );

    resizeTabs();

    redrawOverview();
}

function ajaxRemoveSupportValueAnnotation() {

    var localGlobal = globals;

    $.ajax( { url: '/' + localGlobal.applicationName + '/default/removeSupportValueAnnotation',
              type: "POST",
              data: { treeId: localGlobal.treeId },
              success: removeSupportValueAnnotation } );
}

function removeSupportValueAnnotation() {

    globals.supportValueInfo.threshold = undefined;
    clearSupportValueAnnotation();
}

function clearSupportValueAnnotation() {

    var localGlobal = globals;

    var mainPath = localGlobal.pathObjs.main.supportValue;
    if( mainPath ) { mainPath.remove(); }

    var overviewPath = localGlobal.pathObjs.overview.supportValue;
    if( overviewPath ) { overviewPath.remove(); }
}

function ajaxThickenSupportValueClades() {

    var localGlobal = globals;

    $.ajax( { url: '/' + localGlobal.applicationName + '/default/addSupportValueAnnotation',
              type: "POST",
              data: { treeId: localGlobal.treeId, threshold: $('#supportValueThreshold').val(), thickness: $('#thicknessToAdd').val() },
              success: handleThickenSupportValueResponse } );
}

function handleThickenSupportValueResponse( string ) {
    
    var localGlobal = globals;
    var localConfig = config;

    var [ threshold, thickness ] = string.split(',');

    clearSupportValueAnnotation();

    localGlobal.supportValueInfo.threshold = threshold;
    localGlobal.supportValueInfo.thckness = thickness;

    var pathStrings = { main: new StringBuffer(), overview: new StringBuffer() };
    var pathColor = localConfig.cladeColor.value;

    thickenSupportValueClades( { clade: localGlobal.tree, scaling: localGlobal.scaling, overviewScale: localGlobal.overviewScale, threshold: threshold,
                                 pathWidth: localConfig.pathWidth.value, pathStrings: pathStrings } );

    localGlobal.pathObjs.main.supportValue = createTreePath( { string: pathStrings.main.toString(),
                                                               canvas: localGlobal.r,
                                                               attr: { stroke: pathColor, "stroke-width": thickness } } );
        
    localGlobal.pathObjs.overview.supportValue = createTreePath( { string: pathStrings.overview.toString(),
                                                                  canvas: localGlobal.overviewR,
                                                                  attr: { stroke: pathColor, "stroke-width": thickness * localGlobal.overviewScale.y } } );
}

function thickenSupportValueClades( p ) {

    if( p.clade.name && parseInt( p.clade.name ) >= p.threshold ) {

        addToSupportValueStrings( { clade: p.clade,
                                    scaling: p.scaling,
                                    overviewScale: p.overviewScale,
                                    pathWidth: p.pathWidth,
                                    pathStrings: p.pathStrings } );
    }

    for( var i = 0, ii = p.clade.children.length; i < ii; i++ ) {
        thickenSupportValueClades( { clade: p.clade.children[i],
                                     scaling: p.scaling,
                                     threshold: p.threshold,
                                     pathWidth: p.pathWidth,
                                     overviewScale: p.overviewScale,
                                     pathStrings: p.pathStrings } );
    }
}
